/**
 * @file
 * @author dousha 
 * @date 2020/6/12.
 */

#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"

#include "gpio.h"

/**
 * 初始化 GPIO 驱动层。
 */
void gpio_init() {
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
}

GPIO_TypeDef *gpio_get_group(PeripheralIdentifier identifier) {
    uint8_t groupSelector = (uint8_t) (identifier >> 16u);
    GPIO_TypeDef *group;
    switch (groupSelector) {
        case 0x01u:
        group = GPIOA;
        break;
        case 0x02u:
        group = GPIOB;
        break;
        case 0x03u:
        group = GPIOC;
        break;
        case 0x04u:
        group = GPIOD;
        break;
        case 0x05u:
        group = GPIOE;
        break;
        default:
        return 0;
    }
    return group;
}

uint32_t gpio_get_pin(PeripheralIdentifier identifier) {
    uint32_t pin = 0x1u;
    pin <<= ((identifier & 0x0fu) + 1u);
    return pin;
}

void gpio_preconfigure(PeripheralIdentifier identifier, GPIO_InitTypeDef *gpio) {
    gpio->GPIO_Pin = gpio_get_pin(identifier);
    gpio->GPIO_Speed = GPIO_Speed_50MHz;
}

/**
 * 将给定 GPIO 外设作为输入。
 * 
 * 如果给定的外设不存在，或者给定的模式不支持，设置将会被忽略。
 * 
 * @param identifier 外设标识符
 * @param mode 输入模式
 */
void gpio_as_input(PeripheralIdentifier identifier, GpioInputMode mode) {
    GPIO_InitTypeDef gpio;
    gpio_preconfigure(identifier, &gpio);
    gpio.GPIO_Mode = GPIO_Mode_IN;
    switch (mode) {
        case INPUT_FLOATING:
        gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
        break;
        case INPUT_INTERNALLY_PULLED_DOWN:
        gpio.GPIO_PuPd = GPIO_PuPd_DOWN;
        break;
        case INPUT_INTERNALLY_PULLED_UP:
        gpio.GPIO_PuPd = GPIO_PuPd_UP;
        break;
        case INPUT_ANALOG:
        gpio.GPIO_Mode = GPIO_Mode_AIN;
        break;
        default:
        break;
    }
    GPIO_Init(gpio_get_group(identifier), &gpio);
}

/**
 * 将给定 GPIO 外设作为输出。
 * 
 * 如果给定的外设不存在，或者给定的模式不支持，设置将会被忽略。
 * 
 * @param identifier 外设标识符
 * @param mode 输出模式
 */
void gpio_as_output(PeripheralIdentifier identifier, GpioOutputMode mode) {
    GPIO_InitTypeDef gpio;
    gpio_preconfigure(identifier, &gpio);
    gpio.GPIO_Mode = GPIO_Mode_OUT;
    switch (mode) {
        case OUTPUT_PUSH_AND_PULL:
        gpio.GPIO_OType = GPIO_OType_PP;
        break;
        case OUTPUT_OPEN_DRAIN:
        gpio.GPIO_OType = GPIO_OType_OD;
        case OUTPUT_OPEN_COLLECTOR:
        // not supported
        return;
        default:
        break;
    }
    GPIO_Init(gpio_get_group(identifier), &gpio);
}

/**
 * 设置引脚状态。
 * 
 * 如果该引脚没有被初始化，则行为未定义。
 * 
 * @param identifier 外设标识符
 * @param state 状态
 */
void gpio_write(PeripheralIdentifier identifier, GpioState state) {
    GPIO_TypeDef *group = gpio_get_group(identifier);
    uint32_t pin = gpio_get_pin(identifier);
    switch (state) {
        case HIGH:
        case SOURCE:
        case GROUND:
        GPIO_SetBits(group, pin);
        break;
        case LOW:
        case HIGH_Z:
        GPIO_ResetBits(group, pin);
        default:
        break;
    }
}

/**
 * 读取引脚的状态。
 * 
 * 如果该引脚没有被初始化，则行为未定义。
 * 
 * 如果该引脚为模拟输入引脚或者输出引脚，则行为未定义。
 * 
 * @param identifier 外设标识符
 * @return 引脚状态，总为 HIGH 或者 LOW.
 */
GpioState gpio_read(PeripheralIdentifier identifier) {
    GPIO_TypeDef *group = gpio_get_group(identifier);
    uint32_t pin = gpio_get_pin(identifier);
    if (GPIO_ReadInputDataBit(group, pin)) {
        return HIGH;
    } else {
        return LOW;
    }
}
